Flutter 馃挋 Lua: Using Lua in your Flutter apps 您所在的位置:网站首页 flutter blog Flutter 馃挋 Lua: Using Lua in your Flutter apps

Flutter 馃挋 Lua: Using Lua in your Flutter apps

#Flutter 馃挋 Lua: Using Lua in your Flutter apps | 来源: 网络整理| 查看: 265

This article is written by Maksim Lin

Welcome to another edition of #FlutterFunFriday! In this instalment we’ll be learning how to use Lua in our Flutter apps. So grab a beverage of your choice, fire up your favourite IDE and lets have some fun!

Lua ?

Lua is a small scripting language that is widely used in games, from small hobby projects all the way through to big, well known commercial titles. As more games are written in Flutter, the need for a scripting language for games made in Flutter also grows.

Making use of Lua in Flutter apps is possible in a few different ways, but the specific one I want to cover here is by using the LuaDardo package, an implementation of a Lua 5.3 compatible LuaVM written in pure Dart.

The OG

The reason why this article even exists is because while the original Lua VM in C was meant to be easy to embed in C programs, the embedding API, especially the way to call Lua from its host application and for Lua to call into its host is both not immediately obvious nor is it’s Dart specific variant documented in the LuaDardo project. Luckily for us, LuaDardo uses pretty much the same embedding API as does the original C version of Lua and its API is well documented in the “Lua Book”Programming in Lua the first edition of which is available online.

So “all” that is left for us to do is to translate the C-based API into it’s equivalent Dart implementation provided by LuaDardo.

The Lua Stack

We start with the most important concept in interfacing with Lua, the Lua stack. The stack is the means by which we can send and receive data to and from Lua. The Lua API provides us with various functions (methods in LuaDardo) of manipulating that stack.

As the name suggests, the stack is a data structure that allows both the host code (in this case our Dart code) as well as the LuaVM implementation the ability to push and pop data items onto and off the stack as shown in the diagram below:

The details of what and when things get pushed and popped from this stack we will cover with examples in the rest of this article.

Getting started

To begin with, lets start with the simplest example of just being able to load and initialise a Lua script using LuaDardo. This involves first initialising the LuaVm’s state:

final state = LuaState.newState();

then we can use this state object to load the Lua code (a “chunk” in Lua parlance) from a string:

final code = "print('hello from Lua')"; state.loadString(code);

and then we can cause Lua to evaluate that code chunk:

state.call(0, 0);

However doing so will cause us to have an exception be thrown:

Unhandled Exception: Exception: not function!

This helps illustrate an important point with using Lua, by default no Lua standard library functions are loaded into the newly initialised VM, not even print()! We could fix this by calling:

state.openLibs();

before call() which would cause the VM to load LuaDardos standard library function implementations including print, but I consider not having any standard library functions available a very good feature of using Lua, especially if you intend to use it to run user supplied, untrusted code! As doing so means there is much less chance that users code can break out of the Lua sandbox as only the functions that you explicitly provide will be made available.

How to build Flutter apps on Codemagic How to build Flutter apps on Codemagic Start now Calling from Lua into Dart

Following the above course, we can now provide an implementation of the print() function to our Lua script by calling the method to register the Dart function with the state:

int _printFromLua(LuaState ls) { final mesg = ls.checkString(1); // now we have them, pop args off the Lua stack ls.pop(1); debugPrint("[LUA] $mesg"); return 1; } ... state.register("print", _printFromLua);

Note that all functions that are registered with the state object must implement the function signature of: int Function(LuaState) as shown above. In the simple case of the print function above, the Dart implementation uses 2 methods: checkString() and pop() which will allow us to examine now the details of using the Lua stack.

So what happens when we evaluate the Lua chunk and hence the global call to print() occurs? Well firstly the LuaVM will push the argument to the print function, in this case the string ‘hello from Lua’ onto the stack:

The LuaVM will then call back into the Dart function we registered for the name print. The LuaState object that gets passed into the callback to printFromLua() essentially represents the Lua stack. So when we call:

final mesg = ls.checkString(1);

The checkString() method, we pass it the index of the item we want to read from the stack, in this case index 1. Lua also provides a way to use indexes relative to the top of the stack, decrementing down, which is often a more convienent way to address items on the stack, so in this case we just as easily have used -1 as the index to represent the item at the top of the stack (and hence -2 would be the 2nd top-most item on the stack and so forth).

As the name might suggest, checkString() not only reads and returns an item from the stack, but also sanity checks that the item has the expected type, in this case a string.

However, just reading the item does not manipulate the stack in any way, so that item remains on the stack afterwards. But Lua requires us to clean up after ourselves, so before our callback method completes, we want to remove the item we have used hence the call to pop it off to clear the stack:

ls.pop(1);

This cleanup is essential when dealing with Lua, as it is quite likely that the state could continue to be used as the script continues to run and more Lua code is executed and I have found a common source of errors when using LuaDardo is forgetting to tidy up after having made use of the stack. Luckily LuaDardo provides a very handy extension method to print to the Dart/Flutter debug console the current state of the stack:

ls.printStack(); Calling from Dart into Lua

Now that we have the Lua code loading in the LuaVM, being evaluated and calling a function implemented in Dart code, lets look at going the other way and having Dart call a Lua function.

To invoke a Lua (global) function from Dart, what is required is to first “lookup” the function in the Lua VM, for instance if we want to invoke a function called onUpdate:

const funcName = "onUpdate"; final t = state.getGlobal(funcName);

We should always check that the found symbol actually is a function:

if (t != LuaType.luaFunction) { print("Lua type error, expected a function but got [$t] ${state.toStr(-1)}"); return; }

And then to invoke the function it is:

final r = state.pCall(0, 0, 1);

though again we should always make sure we check for any error conditions:

if (r != ThreadStatus.lua_ok) { print("Lua error calling $funcName: ${state.toStr(-1)}"); return; } Table stacks: passing array data from Lua to Dart

While the technique we covered above works for single pieces of data, what about if we need to send larger amounts of data, for example arrays? This is also possible with the Lua API, but is a bit unintuitive, so I will cover it here as a more advanced use of the Lua stack.

Unlike in some other languages, Lua treats arrays as a special case of its more general Table data structure (in fact almost everything in Lua turns out to be implemented as a Table!)

To illustrate passing a array from Lua to Dart, we’ll use an array of Integer’s that might for example represent a simple bitmap we may wish to display on the Flutter side.

First off we need a piece of array data on the Lua side which we can send to Dart by calling a method which we will name simply draw():

local data = {0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1} draw(data)

In Dart, we can “wire up” this draw function as we did with the print function earlier:

state.register("draw", _draw);

And then in our Dart _draw method we define the expected function signature, check that the first item on the Lua stack is a table type as expected and that we can get a valid length for the table:

int _draw(LuaState ls) { // first check that the top of the stack is a table if (!ls.isTable(1)) { ls.printStack(); //debug: print out current Lua stack throw Exception("Did not find expected Table data from Lua on stack"); } final bitMapDataLength = ls.len2(-1); //get size of table if (bitMapDataLength == null) { throw Exception("Did not find expected Table length from Lua on stack"); } ...

Once we have that we can continue on to the tricky bit of calling getTable() method to access each item in the array (aka the table):

for (int i = 0; i


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有